package com.ruoyi.washer.common;

import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson2.JSONObject;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.domain.SysConfig;
import com.ruoyi.system.mapper.SysConfigMapper;
import com.ruoyi.washer.domain.XwDevice;
import com.ruoyi.washer.domain.XwProduct;
import com.ruoyi.washer.domain.XwPurchaseOrder;
import com.ruoyi.washer.mapper.XwDeviceMapper;
import com.ruoyi.washer.mapper.XwProductMapper;
import com.ruoyi.washer.mapper.XwPurchaseOrderMapper;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.Security;
import java.security.Signature;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;

@Service
public class PaymentPayServiceImpl {

    private final ObjectMapper objectMapper = new ObjectMapper();;

    @Value("${wx.appid}")
    private String appid;

    @Value("${wx.appsecret}")
    private String secret;

    @Value("${wx.mch_id}")
    private String mchId;

    @Value("${wx.key_v2}")
    private String key_v2;

    @Value("${wx.key_v3}")
    private String key_v3;

    @Value("${wx.notify_url}")
    private String notify_url;

    @Value("${wx.refund_notify_url}")
    private String refund_notify_url;

    @Value("${wx.private_key}")
    private String private_key;

    @Value("${wx.mch_serial_no}")
    private String mch_serial_no;

    @Value("${wx.service_id}")
    private String serviceId;

    private static final Logger logger = LoggerFactory.getLogger(PaymentPayServiceImpl.class);



    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private XwDeviceMapper xwDeviceMapper;

    @Autowired
    private XwProductMapper xwProductMapper;

    @Autowired
    private XwPurchaseOrderMapper xwPurchaseOrderMapper;

    @Autowired
    private SysConfigMapper sysConfigMapper;

    @Autowired
    private CloseableHttpClient httpClient;

    @Transactional
    public JSONObject initBuyPay(String deviceSn, int index, String productId, String openId)throws Exception{
        JSONObject jsonObject;
        XwDevice xwDevice=xwDeviceMapper.selectXwDeviceByDeviceSn(deviceSn);
        XwProduct xwProduct=xwProductMapper.selectXwProductByProductId(Long.parseLong(productId));
        int amount=(int)(Float.parseFloat(xwProduct.getProductPrice())*100);
        jsonObject=createOrder(openId,"购买洗衣球",amount);
        XwPurchaseOrder xwPurchaseOrder=new XwPurchaseOrder();
        xwPurchaseOrder.setPayDate(new Date());
        xwPurchaseOrder.setPayType("2");
        xwPurchaseOrder.setOpenId(openId);
        xwPurchaseOrder.setDeviceType(xwDevice.getDeviceType());
        xwPurchaseOrder.setBuyType("2");
        xwPurchaseOrder.setDeviceSn(deviceSn);
        xwPurchaseOrder.setPayState("1");
        xwPurchaseOrder.setPayAmount(new BigDecimal(amount));
        xwPurchaseOrder.setOutTradeNo(jsonObject.getString("outTradeNo"));
        xwPurchaseOrder.setCreatedTime(new Date());
        xwPurchaseOrder.setRefundState("1");
        xwPurchaseOrder.setDivideState("1");
        xwPurchaseOrder.setFinishState("N");
        xwPurchaseOrderMapper.insertXwPurchaseOrder(xwPurchaseOrder);
        return jsonObject;
    }

    public JSONObject createPayScoreOrder(String openid, String description, int money) throws Exception {
        Security.setProperty("crypto.policy", "unlimited");
        //唯一的内部订单号（后续支付、关单、查单、退款都跟这个相关）
        // String outTradeNo = stringToMD5(System.currentTimeMillis() + "" + RandomUtil.randomString(32));
        String outTradeNo =getOutTradeNo();
        SysConfig sysConfig=sysConfigMapper.checkConfigKeyUniqueNew("xw_charging_details");
        String xw_charging_details  = sysConfig.getConfigName();
        logger.info("outTradeNo=========="+outTradeNo);
//			如果用字符串存储私钥就用这个
        HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/payscore/serviceorder");
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-type", "application/json; charset=utf-8");

        ObjectMapper objectMapper = new ObjectMapper();

        Map<String,Object> rootNode = new HashMap<>();
        rootNode.put("mchid", mchId);
        rootNode.put("appid", appid);
        rootNode.put("service_id",serviceId);
        rootNode.put("service_introduction", description);
        rootNode.put("notify_url", notify_url);
        rootNode.put("out_order_no", outTradeNo);
        Map<String,Object> timeRangeData = new HashMap<>();
        timeRangeData.put("start_time", LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")));
        rootNode.put("time_range", timeRangeData);
        Map<String, Object> riskFundData = new HashMap<>();
        riskFundData.put("name", "DEPOSIT");
        riskFundData.put("amount", 199 * 100);
        rootNode.put("risk_fund", riskFundData);
        rootNode.put("openid", openid);
        rootNode.put("need_user_confirm",true);
        List<Map<String, Object>> postPayments = new ArrayList<>();
        Map<String, Object> objectMap = new HashMap<>();
        objectMap.put("name", "VR眼镜租赁费用");
//        objectMap.put("amount", money);
        objectMap.put("description", xw_charging_details);
        postPayments.add(objectMap);
        rootNode.put("post_payments", postPayments);
        String body = objectMapper.writeValueAsString(rootNode);
        httpPost.setEntity(new StringEntity(body, "UTF-8"));
        logger.info("调用微信支付分创建订单接口:{}", body);
        CloseableHttpResponse response = httpClient.execute(httpPost);
        String bodyAsString = EntityUtils.toString(response.getEntity());
        logger.info("bodyAsString============="+bodyAsString);
        JsonNode jsonNode = objectMapper.readTree(bodyAsString);
        JsonNode orderIdNode = jsonNode.get("order_id");
        if(orderIdNode == null || StringUtils.isBlank(orderIdNode.textValue())) {
            throw new RuntimeException("创建微信支付分订单错误!");
        }
        String timestamp = (System.currentTimeMillis() / 1000) + "";
        String nonce = RandomUtil.randomString(32).toUpperCase();
        //使用字段mch_id、service_id、out_order_no、timestamp、nonce_str、sign_type按照签名生成算法计算得出的签名值。
        Map<String,Object> signData = new TreeMap<>();
        signData.put("mch_id", mchId);
        signData.put("service_id", serviceId);
        signData.put("out_order_no", outTradeNo);
        signData.put("timestamp", timestamp);
        signData.put("nonce_str", nonce);
        signData.put("sign_type", "HMAC-SHA256");
        String collect = signData.entrySet().stream().map(entry -> entry.getKey() + "=" + entry.getValue()).collect(Collectors.joining("&")) + "&key=" + private_key;
        String ciphertext = signWithHmacSHA256(collect, private_key).toUpperCase();
        logger.info("ciphertext=========="+ciphertext);
        JSONObject jsonobject=new JSONObject();
        jsonobject.put("nonceStr", nonce);
        jsonobject.put("mchId", mchId);
        jsonobject.put("serviceId", serviceId);
        jsonobject.put("outOrderNo", outTradeNo);
        jsonobject.put("outTradeNo", outTradeNo);
        jsonobject.put("timeStamp", timestamp);
        jsonobject.put("paySign", ciphertext);
        jsonobject.put("signType", "HMAC-SHA256");
        jsonobject.put("package", jsonNode.get("package").textValue());
        return jsonobject;
    }

    @Transactional
    public JSONObject initRentPay(String deviceSn,
                                  String washballSn,
                                  String openId, String perhourPrice, String payType)throws Exception{
        JSONObject jsonObject;
        XwPurchaseOrder xwPurchaseOrder01=xwPurchaseOrderMapper.getClientOrdersByWashballSn(washballSn);
        if(xwPurchaseOrder01!=null){
            jsonObject=new JSONObject();
            jsonObject.put("flag",true);
            return jsonObject;
        }

        XwDevice xwDevice=xwDeviceMapper.selectXwDeviceByDeviceSn(deviceSn);
        if(xwDevice == null){
            jsonObject=new JSONObject();
            jsonObject.put("flag",true);
            return jsonObject;
        }
        SysConfig sysConfig=sysConfigMapper.checkConfigKeyUniqueNew("xw_antecedent_money");
        int amount=(int)(Float.parseFloat(sysConfig.getConfigValue())*100);
        String payTypeCode;
        if (StringUtils.equals("payscore", payType)) {
            payTypeCode = "1";
            jsonObject = createPayScoreOrder(openId, "出租VR眼镜", amount);
        } else {
            payTypeCode = "2";
            jsonObject = createOrder(openId, "出租洗衣球", amount);
        }
        XwPurchaseOrder xwPurchaseOrder=new XwPurchaseOrder();
        xwPurchaseOrder.setPayDate(new Date());
        xwPurchaseOrder.setPayType(payTypeCode);
        xwPurchaseOrder.setOpenId(openId);
        xwPurchaseOrder.setDeviceType(xwDevice.getDeviceType());
        xwPurchaseOrder.setBuyType("1");
        xwPurchaseOrder.setDeviceSn(deviceSn);
        xwPurchaseOrder.setWashballSn(washballSn);
        xwPurchaseOrder.setPayState("1");
        xwPurchaseOrder.setPayAmount(new BigDecimal(amount));
        xwPurchaseOrder.setOutTradeNo(jsonObject.getString("outTradeNo"));
        xwPurchaseOrder.setCreatedTime(new Date());
        xwPurchaseOrder.setRefundState("1");
        xwPurchaseOrder.setDivideState("1");
        xwPurchaseOrder.setFinishState("N");
        xwPurchaseOrder.setRentStartTime(new Date());
        xwPurchaseOrder.setPerhourPrice(new BigDecimal(perhourPrice));
        xwPurchaseOrderMapper.insertXwPurchaseOrder(xwPurchaseOrder);
        jsonObject.put("flag",false);
        return jsonObject;
    }

    public JSONObject createOrder(String openid, String description, int money) throws Exception {
        try {
            Security.setProperty("crypto.policy", "unlimited");
            String outTradeNo =getOutTradeNo();
            HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi");
            httpPost.addHeader("Accept", "application/json");
            httpPost.addHeader("Content-type", "application/json; charset=utf-8");

            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectMapper objectMapper = new ObjectMapper();

            ObjectNode rootNode = objectMapper.createObjectNode();
            rootNode.put("mchid", mchId)
                    .put("appid", appid)
                    .put("description", description)
                    .put("notify_url", notify_url)
                    .put("out_trade_no", outTradeNo);
            rootNode.putObject("amount")
                    .put("total", money);
            rootNode.putObject("payer")
                    .put("openid", openid);
            rootNode.putObject("settle_info")
                    .put("profit_sharing", true);

            objectMapper.writeValue(bos, rootNode);

            httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
            CloseableHttpResponse response = httpClient.execute(httpPost);

            String bodyAsString = EntityUtils.toString(response.getEntity());
            System.out.println("bodyAsString============="+bodyAsString);
            String timestamp = (System.currentTimeMillis() / 1000) + "";
            String nonce = RandomUtil.randomString(32).toUpperCase();
            StringBuilder builder = new StringBuilder();
            builder.append(appid).append("\n");
            builder.append(timestamp).append("\n");
            builder.append(nonce).append("\n");
            JsonNode node = objectMapper.readTree(bodyAsString);
            builder.append("prepay_id="
                    + node.get("prepay_id").toString().substring(1, node.get("prepay_id").toString().length() - 1))
                    .append("\n");
            String ciphertext = sign(builder.toString().getBytes(StandardCharsets.UTF_8));
            System.out.println("ciphertext=========="+ciphertext);
            System.out.println("prepay_id=========="+node.get("prepay_id").toString());
            /*
            Map<String, String> map = new HashMap<String, String>();
            map.put("nonceStr", nonce);
            map.put("package", "prepay_id="
                    + node.get("prepay_id").toString().substring(1, node.get("prepay_id").toString().length() - 1));
            map.put("timeStamp", timestamp);
            map.put("paySign", ciphertext);
            map.put("signType", "RSA");
            map.put("outTradeNo", outTradeNo);
            //小程序前端获取pay就可以使用wx.requestPayment了
             */
            JSONObject JSONObject=new JSONObject();
            JSONObject.put("nonceStr", nonce);
            JSONObject.put("package", "prepay_id="
                    + node.get("prepay_id").toString().substring(1, node.get("prepay_id").toString().length() - 1));
            JSONObject.put("timeStamp", timestamp);
            JSONObject.put("paySign", ciphertext);
            JSONObject.put("signType", "RSA");
            JSONObject.put("outTradeNo", outTradeNo);
            return JSONObject;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /*message为appId、timeStamp、nonceStr、package拼接成的字符串 计算得出paySign*/
    String sign(byte[] message) throws Exception{
        try {
            PrivateKey merchantPrivateKey = PemUtil
                    .loadPrivateKey(new ByteArrayInputStream(private_key.getBytes("utf-8")));
            Signature sign = Signature.getInstance("SHA256withRSA");
            sign.initSign(merchantPrivateKey);
            sign.update(message);
            return Base64.getEncoder().encodeToString(sign.sign());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static String signWithHmacSHA256(String message, String secret) {
        try {
            Mac hmac = Mac.getInstance("HmacSHA256");
            SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
            hmac.init(secret_key);
            byte[] bytes = hmac.doFinal(message.getBytes());
            logger.info("service sign is "+byteArrayToHexString(bytes));
            return byteArrayToHexString(bytes);
        } catch (Exception ex) {
            logger.error("service sign error", ex);
           throw new RuntimeException("签名计算错误!");
        }
    }

    private static String byteArrayToHexString(byte[] bytes) {
        StringBuilder hs = new StringBuilder();
        String tempStr;
        for (int index = 0; bytes != null && index < bytes.length; index++) {
            tempStr = Integer.toHexString(bytes[index] & 0XFF);
            if (tempStr.length() == 1) {
                hs.append('0');
            }
            hs.append(tempStr);
        }
        return hs.toString().toLowerCase();
    }
    /*
    支付成功后的回调接口
 */
    public void payNotifyAll(HttpServletRequest request)throws Exception{
        String encryptedBody=getBody(request);
        String decryptedBody=decryptOrder(encryptedBody);
        logger.info("收到支付回调:{}", decryptedBody);
        JSONObject jsonObject=JSONObject.parseObject(decryptedBody);
        String serviceId = jsonObject.getString("service_id");
        if(StringUtils.isBlank(serviceId)) {
            String trade_state=jsonObject.getString("trade_state");
            if(trade_state!=null&&trade_state.equals("SUCCESS")){
                String transactionId=jsonObject.getString("transaction_id");
                String total=jsonObject.getJSONObject("amount").getString("total");
                String outTradeNo=jsonObject.getString("out_trade_no");
                XwPurchaseOrder xwPurchaseOrder=xwPurchaseOrderMapper.selectXwPurchaseOrderByOutTradeNo(outTradeNo);
                xwPurchaseOrder.setPrepayId(transactionId);
                xwPurchaseOrder.setPayState("3");//支付成功
                xwPurchaseOrder.setPayAmountReal(new BigDecimal(total));
                xwPurchaseOrder.setOutTradeNo(outTradeNo);
                xwPurchaseOrder.setUpdatedTime(new Date());
                if(xwPurchaseOrder.getBuyType().equals("2")){
                    xwPurchaseOrder.setFinishState("Y");
                }
                xwPurchaseOrderMapper.updateXwPurchaseOrder(xwPurchaseOrder);
            }
        } else {
            // 信用分回调
            String trade_state = jsonObject.getString("state");
            String transactionId = jsonObject.getString("order_id");
            String outTradeNo = jsonObject.getString("out_order_no");
            XwPurchaseOrder xwPurchaseOrder = xwPurchaseOrderMapper.selectXwPurchaseOrderByOutTradeNo(outTradeNo);
            if ("DOING".equals(trade_state)) { // 服务订单进行中
                String total = jsonObject.getString("total_amount");
                xwPurchaseOrder.setPrepayId(transactionId);
                xwPurchaseOrder.setPayState("3");//支付成功
                xwPurchaseOrder.setPayAmountReal(new BigDecimal(total));
                xwPurchaseOrder.setOutTradeNo(outTradeNo);
                xwPurchaseOrder.setUpdatedTime(new Date());
                xwPurchaseOrderMapper.updateXwPurchaseOrder(xwPurchaseOrder);
            } else if ("REVOKED".equals(trade_state)) { //商户取消服务订单
                String total = jsonObject.getString("total_amount");
                xwPurchaseOrder.setPayAmountReal(new BigDecimal(total));
                xwPurchaseOrder.setUpdatedTime(new Date());
                xwPurchaseOrder.setIsCancel("1");
                xwPurchaseOrderMapper.updateXwPurchaseOrder(xwPurchaseOrder);
            } else if ("DONE".equals(trade_state)) { //服务订单已完成
                String total = jsonObject.getString("total_amount");
                xwPurchaseOrder.setPrepayId(transactionId);
                xwPurchaseOrder.setPayState("3");//支付成功
                xwPurchaseOrder.setPayAmountReal(new BigDecimal(total));
                xwPurchaseOrder.setOutTradeNo(outTradeNo);
                xwPurchaseOrder.setUpdatedTime(new Date());
                if (xwPurchaseOrder.getBuyType().equals("2")) {
                    xwPurchaseOrder.setFinishState("Y");
                }
                xwPurchaseOrderMapper.updateXwPurchaseOrder(xwPurchaseOrder);
            } else if("EXPIRED".equals(trade_state)) { //服务订单已失效
                String total = jsonObject.getString("total_amount");
                xwPurchaseOrder.setPayAmountReal(new BigDecimal(total));
                xwPurchaseOrder.setUpdatedTime(new Date());
                xwPurchaseOrder.setIsCancel("1");
                xwPurchaseOrder.setPayState("4");
                xwPurchaseOrder.setFinishState("Y");
                xwPurchaseOrder.setRefundAmount(BigDecimal.ZERO);
                xwPurchaseOrder.setRefundAmountReal(BigDecimal.ZERO);
                xwPurchaseOrder.setRentEndTime(new Date());
                xwPurchaseOrderMapper.updateXwPurchaseOrder(xwPurchaseOrder);
            }
        }
    }

    /*支付通知和退款通知给服务器的回调 解密报文*/
    public String decryptOrder(String body) {
        try {
            AesUtil util = new AesUtil(key_v3.getBytes(StandardCharsets.UTF_8));
            ObjectMapper objectMapper = new ObjectMapper();
            JsonNode node = objectMapper.readTree(body);
            JsonNode resource = node.get("resource");
            String ciphertext = resource.get("ciphertext").textValue();
            String associatedData = resource.get("associated_data").textValue();
            String nonce = resource.get("nonce").textValue();
            return util.decryptToString(associatedData.getBytes("utf-8"), nonce.getBytes("utf-8"), ciphertext);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /*
    获取body
     */
    public String getBody(HttpServletRequest request)throws Exception{
        String body="";
        BufferedReader reader=null;
        try {
            reader = request.getReader();
            String line;
            StringBuilder inputString = new StringBuilder();
            while ((line = reader.readLine()) != null) {
                inputString.append(line);
            }
            body = inputString.toString();
            reader.close();
        }catch (Exception e){
            e.printStackTrace();
            throw e;
        }finally {
            if(reader!=null){
                reader.close();
                reader=null;
            }
        }
        return body;
    }

    public  String getOutTradeNo(){
        Long times= System.currentTimeMillis();
        String key = "wx_OutTradeNo_key" ;
        Object obj=redisTemplate.opsForValue().get(key);
        Long id=0L;
        if(obj==null){
            redisTemplate.opsForValue().set(key,times);
            id=redisTemplate.opsForValue().increment(key,1);
        }else{
            id=redisTemplate.opsForValue().increment(key,1);
        }
        return ""+id+RandomUtil.randomNumbers(7);
    }

    public AjaxResult queryOrder(String outOrderNo, String queryId) {
        if (StringUtils.isAllBlank(outOrderNo, queryId)) {
            return AjaxResult.error("请求参数不完整!");
        }
        StringBuilder urlBuilder = new StringBuilder("https://api.mch.weixin.qq.com/v3/payscore/serviceorder?appid=" + appid + "&service_id" + serviceId);
        if (StringUtils.isNotBlank(queryId)) {
            urlBuilder.append("&query_id=").append(queryId);
        }
        if (StringUtils.isNotBlank(outOrderNo)) {
            urlBuilder.append("&out_order_no=").append(outOrderNo);
        }
        HttpGet httpGet = new HttpGet(urlBuilder.toString());
        httpGet.addHeader("Accept", "application/json");
        httpGet.addHeader("Content-type", "application/json; charset=utf-8");
        try {
            CloseableHttpResponse response = httpClient.execute(httpGet);
            String bodyAsString = EntityUtils.toString(response.getEntity());
            logger.info("查询订单:{}-{}:返回信息:{}", outOrderNo, queryId, bodyAsString);
            return AjaxResult.success("查询成功", objectMapper.readTree(bodyAsString));
        } catch (Exception e) {
            logger.error("查询订单信息:{}-{},错误:", outOrderNo, queryId, e);
            return AjaxResult.error("订单查询错误:" + e.getMessage());
        }
    }

}
